/********************************************************************* * * Copyright (C) 2002 Andrew Khan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***************************************************************************/ package jxl.write.biff; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import jxl.common.Assert; import jxl.common.Logger; import jxl.biff.BaseCompoundFile; import jxl.biff.IntegerHelper; import jxl.read.biff.BiffException; /** * Writes out a compound file * * Header block is -1 * Excel data is e..n (where e is the head extension blocks, normally 0 and * n is at least 8) * Summary information (8 blocks) * Document summary (8 blocks) * BBD is block p..q (where p=e+n+16 and q-p+1 is the number of BBD blocks) * Property storage block is q+b...r (normally 1 block) (where b is the number * of BBD blocks) */ final class CompoundFile extends BaseCompoundFile { /** * The logger */ private static Logger logger = Logger.getLogger(CompoundFile.class); /** * The stream to which the jumbled up data is written to */ private OutputStream out; /** * The organized biff records which form the actual excel data */ private ExcelDataOutput excelData; /** * The size of the array */ private int size; /** * The size the excel data should be in order to comply with the * general compound file format */ private int requiredSize; /** * The number of blocks it takes to store the big block depot */ private int numBigBlockDepotBlocks; /** * The number of blocks it takes to store the small block depot chain */ private int numSmallBlockDepotChainBlocks; /** * The number of blocks it takes to store the small block depot */ private int numSmallBlockDepotBlocks; /** * The number of extension blocks required for the header to describe * the BBD */ private int numExtensionBlocks; /** * The extension block for the header */ private int extensionBlock; /** * The number of blocks it takes to store the excel data */ private int excelDataBlocks; /** * The start block of the root entry */ private int rootStartBlock; /** * The start block of the excel data */ private int excelDataStartBlock; /** * The start block of the big block depot */ private int bbdStartBlock; /** * The start block of the small block depot */ private int sbdStartBlockChain; /** * The start block of the small block depot */ private int sbdStartBlock; /** * The number of big blocks required for additional property sets */ private int additionalPropertyBlocks; /** * The number of small blocks */ private int numSmallBlocks; /** * The total number of property sets in this compound file */ private int numPropertySets; /** * The number of blocks required to store the root entry property sets * and small block depot */ private int numRootEntryBlocks; /** * The list of additional, non standard property sets names */ private ArrayList additionalPropertySets; /** * The map of standard property sets, keyed on name */ private HashMap standardPropertySets; /** * Structure used to store the property set and the data */ private static final class ReadPropertyStorage { PropertyStorage propertyStorage; byte[] data; int number; ReadPropertyStorage(PropertyStorage ps, byte[] d, int n) { propertyStorage = ps; data = d; number = n; } } // The following member variables are used across methods when // writing out the big block depot /** * The current position within the bbd. Used when writing out the * BBD */ private int bbdPos; /** * The current bbd block */ private byte[] bigBlockDepot; /** * Constructor * * @param l the length of the data * @param os the output stream to write to * @param data the excel data * @param rcf the read compound */ public CompoundFile(ExcelDataOutput data, int l, OutputStream os, jxl.read.biff.CompoundFile rcf) throws CopyAdditionalPropertySetsException, IOException { super(); size = l; excelData = data; readAdditionalPropertySets(rcf); numRootEntryBlocks = 1; numPropertySets = 4 + (additionalPropertySets != null ? additionalPropertySets.size() : 0); if (additionalPropertySets != null) { numSmallBlockDepotChainBlocks = getBigBlocksRequired(numSmallBlocks * 4); numSmallBlockDepotBlocks = getBigBlocksRequired (numSmallBlocks * SMALL_BLOCK_SIZE); numRootEntryBlocks += getBigBlocksRequired (additionalPropertySets.size() * PROPERTY_STORAGE_BLOCK_SIZE); } int blocks = getBigBlocksRequired(l); // First pad the data out so that it fits nicely into a whole number // of blocks if (l < SMALL_BLOCK_THRESHOLD) { requiredSize = SMALL_BLOCK_THRESHOLD; } else { requiredSize = blocks * BIG_BLOCK_SIZE; } out = os; // Do the calculations excelDataBlocks = requiredSize/BIG_BLOCK_SIZE; numBigBlockDepotBlocks = 1; int blockChainLength = (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4; int startTotalBlocks = excelDataBlocks + 8 + // summary block 8 + // document information additionalPropertyBlocks + numSmallBlockDepotBlocks + numSmallBlockDepotChainBlocks + numRootEntryBlocks; int totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // Calculate the number of BBD blocks needed to hold this info numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // Does this affect the total? totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // And recalculate numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // Does this affect the total? totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // See if the excel bbd chain can fit into the header block. // Remember to allow for the end of chain indicator if (numBigBlockDepotBlocks > blockChainLength - 1 ) { // Sod it - we need an extension block. We have to go through // the whole tiresome calculation again extensionBlock = 0; // Compute the number of extension blocks int bbdBlocksLeft = numBigBlockDepotBlocks - blockChainLength + 1; numExtensionBlocks = (int) Math.ceil((double) bbdBlocksLeft / (double) (BIG_BLOCK_SIZE/4 - 1)); // Modify the total number of blocks required and recalculate the // the number of bbd blocks totalBlocks = startTotalBlocks + numExtensionBlocks + numBigBlockDepotBlocks; numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // The final total totalBlocks = startTotalBlocks + numExtensionBlocks + numBigBlockDepotBlocks; } else { extensionBlock = -2; numExtensionBlocks = 0; } // Set the excel data start block to be after the header (and // its extensions) excelDataStartBlock = numExtensionBlocks; // Set the start block of the small block depot sbdStartBlock = -2; if (additionalPropertySets != null && numSmallBlockDepotBlocks != 0) { sbdStartBlock = excelDataStartBlock + excelDataBlocks + additionalPropertyBlocks + 16; } // Set the sbd chain start block to be after the excel data and the // small block depot sbdStartBlockChain = -2; if (sbdStartBlock != -2) { sbdStartBlockChain = sbdStartBlock + numSmallBlockDepotBlocks; } // Set the bbd start block to be after all the excel data if (sbdStartBlockChain != -2) { bbdStartBlock = sbdStartBlockChain + numSmallBlockDepotChainBlocks; } else { bbdStartBlock = excelDataStartBlock + excelDataBlocks + additionalPropertyBlocks + 16; } // Set the root start block to be after all the big block depot blocks rootStartBlock = bbdStartBlock + numBigBlockDepotBlocks; if (totalBlocks != rootStartBlock + numRootEntryBlocks) { logger.warn("Root start block and total blocks are inconsistent " + " generated file may be corrupt"); logger.warn("RootStartBlock " + rootStartBlock + " totalBlocks " + totalBlocks); } } /** * Reads the additional property sets from the read in compound file * * @param readCompoundFile the file read in * @exception CopyAdditionalPropertySetsException * @exception IOException */ private void readAdditionalPropertySets (jxl.read.biff.CompoundFile readCompoundFile) throws CopyAdditionalPropertySetsException, IOException { if (readCompoundFile == null) { return; } additionalPropertySets = new ArrayList(); standardPropertySets = new HashMap(); int blocksRequired = 0; int numPropertySets = readCompoundFile.getNumberOfPropertySets(); for (int i = 0 ; i < numPropertySets ; i++) { PropertyStorage ps = readCompoundFile.getPropertySet(i); boolean standard = false; if (ps.name.equalsIgnoreCase(ROOT_ENTRY_NAME)) { standard = true; ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i); standardPropertySets.put(ROOT_ENTRY_NAME, rps); } // See if it is a standard property set for (int j = 0 ; j < STANDARD_PROPERTY_SETS.length && !standard ; j++) { if (ps.name.equalsIgnoreCase(STANDARD_PROPERTY_SETS[j])) { // See if it comes directly off the root entry PropertyStorage ps2 = readCompoundFile.findPropertyStorage(ps.name); Assert.verify(ps2 != null); if (ps2 == ps) { standard = true; ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i); standardPropertySets.put(STANDARD_PROPERTY_SETS[j], rps); } } } if (!standard) { try { byte[] data = null; if (ps.size > 0 ) { data = readCompoundFile.getStream(i); } else { data = new byte[0]; } ReadPropertyStorage rps = new ReadPropertyStorage(ps, data, i); additionalPropertySets.add(rps); if (data.length > SMALL_BLOCK_THRESHOLD) { int blocks = getBigBlocksRequired(data.length); blocksRequired += blocks; } else { int blocks = getSmallBlocksRequired(data.length); numSmallBlocks += blocks; } } catch (BiffException e) { logger.error(e); throw new CopyAdditionalPropertySetsException(); } } } additionalPropertyBlocks = blocksRequired; } /** * Writes out the excel file in OLE compound file format * * @exception IOException */ public void write() throws IOException { writeHeader(); writeExcelData(); writeDocumentSummaryData(); writeSummaryData(); writeAdditionalPropertySets(); writeSmallBlockDepot(); writeSmallBlockDepotChain(); writeBigBlockDepot(); writePropertySets(); // Don't flush or close the stream - this is handled by the enclosing File // object } /** * Writes out any additional property sets */ private void writeAdditionalPropertySets() throws IOException { if (additionalPropertySets == null) { return; } for (Iterator i = additionalPropertySets.iterator(); i.hasNext() ;) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); byte[] data = rps.data; if (data.length > SMALL_BLOCK_THRESHOLD) { int numBlocks = getBigBlocksRequired(data.length); int requiredSize = numBlocks * BIG_BLOCK_SIZE; out.write(data, 0, data.length); byte[] padding = new byte[requiredSize - data.length]; out.write(padding, 0, padding.length); } } } /** * Writes out the excel data, padding it out with empty bytes as * necessary * Also write out empty * * @exception IOException */ private void writeExcelData() throws IOException { excelData.writeData(out); byte[] padding = new byte[requiredSize - size]; out.write(padding); } /** * Write out the document summary data. This is just blank * * @exception IOException */ private void writeDocumentSummaryData() throws IOException { byte[] padding = new byte[SMALL_BLOCK_THRESHOLD]; // Write out the summary information out.write(padding); } /** * Write out the summary data. This is just blank * * @exception IOException */ private void writeSummaryData() throws IOException { byte[] padding = new byte[SMALL_BLOCK_THRESHOLD]; // Write out the summary information out.write(padding); } /** * Writes the compound file header * * @exception IOException */ private void writeHeader() throws IOException { // Build up the header array byte[] headerBlock = new byte[BIG_BLOCK_SIZE]; byte[] extensionBlockData = new byte[BIG_BLOCK_SIZE * numExtensionBlocks]; // Copy in the identifier System.arraycopy(IDENTIFIER, 0, headerBlock, 0, IDENTIFIER.length); // Copy in some magic values - no idea what they mean headerBlock[0x18] = 0x3e; headerBlock[0x1a] = 0x3; headerBlock[0x1c] = (byte) 0xfe; headerBlock[0x1d] = (byte) 0xff; headerBlock[0x1e] = 0x9; headerBlock[0x20] = 0x6; headerBlock[0x39] = 0x10; // Set the number of BBD blocks IntegerHelper.getFourBytes(numBigBlockDepotBlocks, headerBlock, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); // Set the small block depot chain IntegerHelper.getFourBytes(sbdStartBlockChain, headerBlock, SMALL_BLOCK_DEPOT_BLOCK_POS); // Set the number of blocks in the small block depot chain IntegerHelper.getFourBytes(numSmallBlockDepotChainBlocks, headerBlock, NUM_SMALL_BLOCK_DEPOT_BLOCKS_POS); // Set the extension block IntegerHelper.getFourBytes(extensionBlock, headerBlock, EXTENSION_BLOCK_POS); // Set the number of extension blocks to be the number of BBD blocks - 1 IntegerHelper.getFourBytes(numExtensionBlocks, headerBlock, NUM_EXTENSION_BLOCK_POS); // Set the root start block IntegerHelper.getFourBytes(rootStartBlock, headerBlock, ROOT_START_BLOCK_POS); // Set the block numbers for the BBD. Set the BBD running // after the excel data and summary information int pos = BIG_BLOCK_DEPOT_BLOCKS_POS; // See how many blocks fit into the header int blocksToWrite = Math.min(numBigBlockDepotBlocks, (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4); int blocksWritten = 0; for (int i = 0 ; i < blocksToWrite; i++) { IntegerHelper.getFourBytes(bbdStartBlock + i, headerBlock, pos); pos += 4; blocksWritten++; } // Pad out the rest of the header with blanks for (int i = pos; i < BIG_BLOCK_SIZE; i++) { headerBlock[i] = (byte) 0xff; } out.write(headerBlock); // Write out the extension blocks pos = 0; for (int extBlock = 0; extBlock < numExtensionBlocks; extBlock++) { blocksToWrite = Math.min(numBigBlockDepotBlocks - blocksWritten, BIG_BLOCK_SIZE/4 -1); for(int j = 0 ; j < blocksToWrite; j++) { IntegerHelper.getFourBytes(bbdStartBlock + blocksWritten + j, extensionBlockData, pos); pos += 4; } blocksWritten += blocksToWrite; // Indicate the next block, or the termination of the chain int nextBlock = (blocksWritten == numBigBlockDepotBlocks) ? -2 : extBlock+1 ; IntegerHelper.getFourBytes(nextBlock, extensionBlockData, pos); pos +=4; } if (numExtensionBlocks > 0) { // Pad out the rest of the extension block with blanks for (int i = pos; i < extensionBlockData.length; i++) { extensionBlockData[i] = (byte) 0xff; } out.write(extensionBlockData); } } /** * Checks that the data can fit into the current BBD block. If not, * then it moves on to the next block * * @exception IOException */ private void checkBbdPos() throws IOException { if (bbdPos >= BIG_BLOCK_SIZE) { // Write out the extension block. This will simply be the next block out.write(bigBlockDepot); // Create a new block bigBlockDepot = new byte[BIG_BLOCK_SIZE]; bbdPos = 0; } } /** * Writes out the big block chain * * @param startBlock the starting block of the big block chain * @param numBlocks the number of blocks in the chain * @exception IOException */ private void writeBlockChain(int startBlock, int numBlocks) throws IOException { int blocksToWrite = numBlocks - 1; int blockNumber = startBlock + 1; while (blocksToWrite > 0) { int bbdBlocks = Math.min(blocksToWrite, (BIG_BLOCK_SIZE - bbdPos)/4); for (int i = 0 ; i < bbdBlocks; i++) { IntegerHelper.getFourBytes(blockNumber, bigBlockDepot, bbdPos); bbdPos +=4 ; blockNumber++; } blocksToWrite -= bbdBlocks; checkBbdPos(); } // Write the end of the block chain IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } /** * Writes the block chains for the additional property sets * * @exception IOException */ private void writeAdditionalPropertySetBlockChains() throws IOException { if (additionalPropertySets == null) { return; } int blockNumber = excelDataStartBlock + excelDataBlocks + 16; for (Iterator i = additionalPropertySets.iterator(); i.hasNext() ; ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); if (rps.data.length > SMALL_BLOCK_THRESHOLD) { int numBlocks = getBigBlocksRequired(rps.data.length); writeBlockChain(blockNumber, numBlocks); blockNumber += numBlocks; } } } /** * Writes out the chains for the small block depot */ private void writeSmallBlockDepotChain() throws IOException { if (sbdStartBlockChain == -2) { return; } byte[] smallBlockDepotChain = new byte[numSmallBlockDepotChainBlocks * BIG_BLOCK_SIZE]; int pos = 0; int sbdBlockNumber = 1; for (Iterator i = additionalPropertySets.iterator(); i.hasNext() ; ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); if (rps.data.length <= SMALL_BLOCK_THRESHOLD && rps.data.length != 0) { int numSmallBlocks = getSmallBlocksRequired(rps.data.length); for (int j = 0 ; j < numSmallBlocks - 1 ; j++) { IntegerHelper.getFourBytes(sbdBlockNumber, smallBlockDepotChain, pos); pos += 4; sbdBlockNumber++; } // Write out the end of chain IntegerHelper.getFourBytes(-2, smallBlockDepotChain, pos); pos += 4; sbdBlockNumber++; } } out.write(smallBlockDepotChain); } /** * Writes out all the data in the small block depot * * @exception */ private void writeSmallBlockDepot() throws IOException { if (additionalPropertySets == null) { return; } byte[] smallBlockDepot = new byte[numSmallBlockDepotBlocks * BIG_BLOCK_SIZE]; int pos = 0; for (Iterator i = additionalPropertySets.iterator() ; i.hasNext() ; ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); if (rps.data.length <= SMALL_BLOCK_THRESHOLD) { int smallBlocks = getSmallBlocksRequired(rps.data.length); int length = smallBlocks * SMALL_BLOCK_SIZE; System.arraycopy(rps.data, 0, smallBlockDepot, pos, rps.data.length); pos += length; } } out.write(smallBlockDepot); } /** * Writes out the Big Block Depot * * @exception IOException */ private void writeBigBlockDepot() throws IOException { // This is after the excel data, the summary information, the // big block property sets and the small block depot bigBlockDepot = new byte[BIG_BLOCK_SIZE]; bbdPos = 0; // Write out the extension blocks, indicating them as special blocks for (int i = 0 ; i < numExtensionBlocks; i++) { IntegerHelper.getFourBytes(-3, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } writeBlockChain(excelDataStartBlock, excelDataBlocks); // The excel data has been written. Now write out the rest of it // Write the block chain for the summary information int summaryInfoBlock = excelDataStartBlock + excelDataBlocks + additionalPropertyBlocks; for (int i = summaryInfoBlock; i < summaryInfoBlock + 7; i++) { IntegerHelper.getFourBytes(i + 1, bigBlockDepot, bbdPos); bbdPos +=4 ; checkBbdPos(); } // Write the end of the block chain for the summary info block IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); // Write the block chain for the document summary information for (int i = summaryInfoBlock + 8; i < summaryInfoBlock + 15; i++) { IntegerHelper.getFourBytes(i + 1, bigBlockDepot, bbdPos); bbdPos +=4 ; checkBbdPos(); } // Write the end of the block chain for the document summary IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); // Write out the block chain for the copied property sets, if present writeAdditionalPropertySetBlockChains(); if (sbdStartBlock != -2) { // Write out the block chain for the small block depot writeBlockChain(sbdStartBlock, numSmallBlockDepotBlocks); // Write out the block chain for the small block depot chain writeBlockChain(sbdStartBlockChain, numSmallBlockDepotChainBlocks); } // The Big Block Depot immediately follows. Denote these as a special // block for (int i = 0; i < numBigBlockDepotBlocks; i++) { IntegerHelper.getFourBytes(-3, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } // Write the root entry writeBlockChain(rootStartBlock, numRootEntryBlocks); // Pad out the remainder of the block if (bbdPos != 0) { for (int i = bbdPos; i < BIG_BLOCK_SIZE; i++) { bigBlockDepot[i] = (byte) 0xff; } out.write(bigBlockDepot); } } /** * Calculates the number of big blocks required to store data of the * specified length * * @param length the length of the data * @return the number of big blocks required to store the data */ private int getBigBlocksRequired(int length) { int blocks = length / BIG_BLOCK_SIZE; return (length % BIG_BLOCK_SIZE > 0 )? blocks + 1 : blocks; } /** * Calculates the number of small blocks required to store data of the * specified length * * @param length the length of the data * @return the number of small blocks required to store the data */ private int getSmallBlocksRequired(int length) { int blocks = length / SMALL_BLOCK_SIZE; return (length % SMALL_BLOCK_SIZE > 0 )? blocks + 1 : blocks; } /** * Writes out the property sets * * @exception IOException */ private void writePropertySets() throws IOException { byte[] propertySetStorage = new byte[BIG_BLOCK_SIZE * numRootEntryBlocks]; int pos = 0; int[] mappings = null; // Build up the mappings array if (additionalPropertySets != null) { mappings = new int[numPropertySets]; // Map the standard ones to the first four for (int i = 0 ; i < STANDARD_PROPERTY_SETS.length ; i++) { ReadPropertyStorage rps = (ReadPropertyStorage) standardPropertySets.get(STANDARD_PROPERTY_SETS[i]); if (rps != null) { mappings[rps.number] = i; } else { logger.warn("Standard property set " + STANDARD_PROPERTY_SETS[i] + " not present in source file"); } } // Now go through the original ones int newMapping = STANDARD_PROPERTY_SETS.length; for (Iterator i = additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); mappings[rps.number] = newMapping; newMapping++; } } int child = 0; int previous = 0; int next = 0; // Compute the size of the root property set int size = 0; if (additionalPropertySets != null) { // Workbook size += getBigBlocksRequired(requiredSize) * BIG_BLOCK_SIZE; // The two information blocks size += getBigBlocksRequired(SMALL_BLOCK_THRESHOLD) * BIG_BLOCK_SIZE; size += getBigBlocksRequired(SMALL_BLOCK_THRESHOLD) * BIG_BLOCK_SIZE; // Additional property sets for (Iterator i = additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); if (rps.propertyStorage.type != 1) { if (rps.propertyStorage.size >= SMALL_BLOCK_THRESHOLD) { size += getBigBlocksRequired(rps.propertyStorage.size) * BIG_BLOCK_SIZE; } else { size += getSmallBlocksRequired(rps.propertyStorage.size) * SMALL_BLOCK_SIZE; } } } } // Set the root entry property set PropertyStorage ps = new PropertyStorage(ROOT_ENTRY_NAME); ps.setType(5); ps.setStartBlock(sbdStartBlock); ps.setSize(size); ps.setPrevious(-1); ps.setNext(-1); ps.setColour(0); child = 1; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) standardPropertySets.get(ROOT_ENTRY_NAME); child = mappings[rps.propertyStorage.child]; } ps.setChild(child); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the workbook property set ps = new PropertyStorage(WORKBOOK_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock); // start the excel data after immediately after this block ps.setSize(requiredSize); // always use a big block stream - none of that messing around // with small blocks previous = 3; next = -1; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) standardPropertySets.get(WORKBOOK_NAME); previous = rps.propertyStorage.previous != -1 ? mappings[rps.propertyStorage.previous] : -1; next = rps.propertyStorage.next != -1 ? mappings[rps.propertyStorage.next] : -1 ; } ps.setPrevious(previous); ps.setNext(next); ps.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the summary information ps = new PropertyStorage(SUMMARY_INFORMATION_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock + excelDataBlocks); ps.setSize(SMALL_BLOCK_THRESHOLD); previous = 1; next = 3; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) standardPropertySets.get(SUMMARY_INFORMATION_NAME); if (rps != null) { previous = rps.propertyStorage.previous != - 1 ? mappings[rps.propertyStorage.previous] : -1 ; next = rps.propertyStorage.next != - 1 ? mappings[rps.propertyStorage.next] : -1 ; } } ps.setPrevious(previous); ps.setNext(next); ps.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the document summary information ps = new PropertyStorage(DOCUMENT_SUMMARY_INFORMATION_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock + excelDataBlocks + 8); ps.setSize(SMALL_BLOCK_THRESHOLD); ps.setPrevious(-1); ps.setNext(-1); ps.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Write out the additional property sets if (additionalPropertySets == null) { out.write(propertySetStorage); return; } int bigBlock = excelDataStartBlock + excelDataBlocks + 16; int smallBlock = 0; for (Iterator i = additionalPropertySets.iterator() ; i.hasNext(); ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); int block = rps.data.length > SMALL_BLOCK_THRESHOLD ? bigBlock : smallBlock; ps = new PropertyStorage(rps.propertyStorage.name); ps.setType(rps.propertyStorage.type); ps.setStartBlock(block); ps.setSize(rps.propertyStorage.size); // ps.setColour(rps.propertyStorage.colour); previous = rps.propertyStorage.previous != -1 ? mappings[rps.propertyStorage.previous] : -1; next = rps.propertyStorage.next != -1 ? mappings[rps.propertyStorage.next] : -1; child = rps.propertyStorage.child != -1 ? mappings[rps.propertyStorage.child] : -1; ps.setPrevious(previous); ps.setNext(next); ps.setChild(child); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; if (rps.data.length > SMALL_BLOCK_THRESHOLD) { bigBlock += getBigBlocksRequired(rps.data.length); } else { smallBlock += getSmallBlocksRequired(rps.data.length); } } out.write(propertySetStorage); } }